#include <iostream>
#include <fstream>
#include <time.h>
#include "VM.h"
#include "VmGroup.h"
#include "Compiler.h"
#include "System.h"
#include "Options.h"

using namespace Vorpal;

#if useThreads
#include "TThreadPool.hh"
#endif

int main (int argc, const char *argv[]) {
	static Options option("usage: vorpal [options] [source_paths]");
	option.Define(false, "--test", "-t", "run internal tests");
	option.Define(false, "--compile-only", "-c", "do not execute code");
	option.Define(false, "--disassemble", "-d", "show compilation result in detail");
	option.Define(false, "--interactive", "-i", "run interactive console");
	option.Define(false, "--benchmark", "-b", "report cpu ticks for execution");
	option.Define(false, "--help", "-h", "show this help text");
	option.Define(false, "--quiet", "-q", "no extra printing");
	option.Define(false, "--arg", "-a", "all remaining args to be passed to script", "", true);
	option.Define(false, "--record-info", "-r", "record additional information useful for debugging scripts");
	option.Define(true, "--save", "-s", "[path] save compiled code to path (only for debugging, not reloadable)");
	option.Define(true, "--pool", "-p", "[num] set the number of threads in the pool, 0 for single threading");
	
	const char *err = option.Read(argc, argv);
	if(err){
		printf("Error reading command line option: %s\n", err);
		exit(1);
	}
	
	if(!option.Has("--quiet")){
	    std::cout << "Vorpal\n-----------\n";
	}
	if(option.Has("--help") || argc < 2){
		cout << option.HelpString();
	}
	if(argc < 2){
		exit(0);
	}
	
	uint numThreads = 1;
	if(option.Has("--pool")){
		numThreads = atoi(option.Get("--pool").c_str());
		Assert(1 <= numThreads && numThreads <= 64);
	}
#if useThreads
		tp_init(numThreads);
#endif
	
	if(option.Has("--test")){
		printf("obj_size = %i\n", int(sizeof(Object)));		
	}
	else if(option.Has("--interactive")){
		// TODO: Move this into a interpreter class...
		printf("Shell Commands\n Type \"help\" for more information.\n Type \"quit\" or \"q\" to exit the vorpal shell\n");
		const int bufferSize = 200;
		char buffer[bufferSize] = "";
		
		System *sys = new System();
		// record arguments for sys.arg(index) and sys.argCount()
		loop(r, 0, option.NumRemaining()){
			sys->AddArgument(option.Remaining(r));
		}
		VM vm(sys);
		
		Compiler c;
		c.EnableDebugInfo( option.Has("--record-info") );
		while(true){
			printf("> ");
			fgets(buffer, bufferSize, stdin);
			if(strlen(buffer) > 1){
				// Quit vorpal on q or quit
				if(strcmp(buffer, "q\n") == 0 || strcmp(buffer, "quit\n") == 0){
					break;
				}
				else{
					if(c.CompileString(buffer) && c.Size() > 0){
						Cell *codeCells = new Cell[c.Size()]; 
						c.Get(codeCells, c.Size());		
						Handle<Code> code( new Code(codeCells) );
						vm.Push(vm.GetSystem()); 
						vm.Execute(code, 1);
						vm.Drop(vm.Depth());
						if(strstr(buffer, "method") == 0){
							delete[] codeCells; // TODO: intentional memory leak because Code objects point to here
						}
					}
					else{
						puts("??");
					}
				}
			}
		}		
	}
	else{
		if(option.NumExtras() > 0){	
			System *sys = new System();
			// record arguments for sys.arg(index) and sys.argCount()
			loop(r, 0, option.NumRemaining()){
				sys->AddArgument(option.Remaining(r));
			}
			VM vm(sys);

			loop(extraIndex, 0, option.NumExtras()){
				Compiler c;
				c.EnableDebugInfo( option.Has("--record-info") );
				//c.TestTokenizer();
                                // compile and execute each specified file
				if(c.Compile(option.Extra(extraIndex).c_str())){
					DeclareArray(codeCells, Cell, c.Size());
					c.Get(codeCells, c.Size());
					Handle< Code > code( new Code(codeCells) );
					
					if(option.Has("--save")){
						FILE *fp = fopen(option.Get("--save").c_str(), "w+");
						if(fp){
							fwrite(codeCells, sizeof(codeCells[0]), c.Size(), fp);
							fclose(fp);
						}
						else{
							cout << "Could not write to: " << option.Get("--save") << endl;
						}
					}
					if(option.Has("--disassemble")){
						puts("-----------");
						printf("code[%u]:\n", c.Size());
						Disassemble(PrintInstruction, codeCells);
						puts("-----------");
					}
					
					if(!option.Has("--compile-only")){
						vm.Push(vm.GetSystem());
						clock_t start = clock();
						vm.Execute(code, 1);  //may throw an exception (int errCode)
						while(TheGroup().RoundRobin()){ }
						int elapsed = clock() - start;
						if(option.Has("--benchmark")){
							printf("elapsed = %i\n", elapsed);
						}
						vm.Drop(vm.Depth());
					}
				}
			}
			vm.GetSystem()->Clear();
		}
		else{
			puts("ERROR: source path to execute was not specified");
			cout << option.HelpString() << endl;
		}
	}
	if(!option.Has("--quiet")){
		std::cout << "-----------\n";
	}
#if useThreads
		tp_done();
#endif
	return 0;
}


